package com.example.llm.sgg.chat.handler;

import com.example.llm.sgg.chat.dto.ChatMessageDTO;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.reactive.socket.WebSocketHandler;
import org.springframework.web.reactive.socket.WebSocketMessage;
import org.springframework.web.reactive.socket.WebSocketSession;
import org.springframework.web.util.UriComponentsBuilder;
import reactor.core.publisher.Mono;
import reactor.core.publisher.SignalType;

@Component
@Slf4j
public class ChatWebSocketMessageHandler implements WebSocketHandler {

    private final RedisMessageSubPublisher redisMessageSubPublisher;
    private final WebSocketSessionManager webSocketSessionManager;
    private final ObjectMapper objectMapper;

    public ChatWebSocketMessageHandler(RedisMessageSubPublisher redisPublisher, WebSocketSessionManager sessionManager, ObjectMapper objectMapper) {
        this.redisMessageSubPublisher = redisPublisher;
        this.webSocketSessionManager = sessionManager;
        this.objectMapper = objectMapper;
    }

    @Override
    public Mono<Void> handle(WebSocketSession session) {
        // 从 URL 参数或认证信息中获取用户ID
        String userId = extractUserId(session);
        log.info("会话开始: {}", userId);

        // 会话开始
        sessionStart(userId, session);

        // 接收客户端消息
        return session.receive()
                .map(WebSocketMessage::getPayloadAsText)
                .map(this::convert2ChatMessage)
                .doOnNext(this::processMessage)
                // 返回一个 Mono<Void>，忽略上游数据，只关心完成
                .then()
                .doFinally(signalType -> sessionEnd(signalType, userId));
    }

    private void sessionStart(String userId, WebSocketSession session) {
        // 绑定用户会话
        webSocketSessionManager.bindUserSession(userId, session);
        // 用户订阅点对点消息
        redisMessageSubPublisher.subscribeFromOther(userId)
                .subscribe(chatMessage -> processRedisMessage(userId, session, chatMessage));
        // 用户订阅广播消息
        redisMessageSubPublisher.subscribeBroadcast()
                .subscribe(chatMessage -> processRedisMessage(userId, session, chatMessage));
    }

    private void processRedisMessage(String userId, WebSocketSession session, ChatMessageDTO chatMessage) {
        String message = null;
        try {
            message = objectMapper.writeValueAsString(chatMessage);
        } catch (JsonProcessingException e) {
            log.error("消息转换异常 object->jsonString", e);
            return;
        }
        log.info("[{}]收到消息: {}", userId, message);
        // 发送消息到客户端
        session.send(Mono.just(session.textMessage(message)))
                .doOnError(err -> log.error("ws发送消息异常", err))
                .subscribe();
    }

    private String extractUserId(WebSocketSession session) {
        return UriComponentsBuilder.fromUri(session.getHandshakeInfo().getUri())
                .build()
                .getQueryParams()
                .getFirst("userId");
    }

    private ChatMessageDTO convert2ChatMessage(String payload) {
        log.info("消息转换: {}", payload);
        try {
            return objectMapper.readValue(payload, ChatMessageDTO.class);
        } catch (JsonProcessingException e) {
            log.error("消息转换异常 jsonString->object", e);
            return ChatMessageDTO.exception(payload);
        }
    }

    private void processMessage(ChatMessageDTO chatMessage) {
        if (ChatMessageDTO.MessageType.EXCEPTION.equals(chatMessage.getType())) {
            return;
        }
        if (StringUtils.hasText(chatMessage.getRecipient())) {
            // 点对点消息
            redisMessageSubPublisher.publishToUser(chatMessage.getRecipient(), chatMessage).subscribe();
        } else {
            // 广播消息
            redisMessageSubPublisher.publishBroadcast(chatMessage).subscribe();
        }
    }

    public void sessionEnd(SignalType signalType, String userId) {
        log.info("会话结束: signalType={} {}", signalType, userId); // signalType=onComplete 1234567890
        // 解绑用户会话
        webSocketSessionManager.unbindUserSession(userId);
        // TODO 离线用户如何接收消息
    }

}